The DRAW() Function Challenge

Winning Entry by Anthony Webb


NL130 Home

DRAW Challenge Winner

Activex DLLs in LB

Chat Window Prototype

Stylebits Corner

SORT Data File

Projectile Motion in 3D

::::::::::::::::::::::::::::::::::::::::::::::::::::

Submission Guildlines

Newsletter Help

Index

Back on January 20th, 2005, forum member IntegerJim posted this intriguing message on the Wishlist board of the forum:

I would like to see a draw command such as was available in Microsoft BASIC - e.g. DRAW "BM200,200;C0U60R60D60L60" this would draw a box. Extremely complex designs are possible. There was also a REDRAW command, if memory serves me, which would act as an eraser. In short, I would like to see all the graphics capabilities that were present with Microsoft BASIC.

FYI: I am a teacher and have written tons of educational programs which I would like to convert to LBASIC.

Quickly, the learned members of the LB community realized that this "wish" could be fulfilled without a new, dedicated Liberty BASIC command. Because Liberty BASIC already has excellent string-parsing functions, it would be entirely possible to custom-build a new function which could parse the type of draw-command strings that IntegerJim identified above.

Within the day, the Emporess of the LB community, Alyce Watson, quickly scoped out a new challenge to write a DRAW() function. Alyce noted that the syntax for Microsoft's original DRAW() function could be found here, and she set February 20-21 as the days on which forum members would vote for their favorite DRAW() Challenge entry.

Unsurprisingly, this challenge was received with great enthusiasm, attracting three pages worth of followup posts! Many users wrote to identify other sources of information on the syntax and use of the DRAW() function as implemented in QBASIC. In the end, the following six forum members crafted DRAW() Challenge entries: RodBird, Janet Terra, Callum Lowcay, Anthony Webb, and Gunther. All of these marvelous entries are available here for download.

When forum voting concluded on February 21st, Anthony Webb's entry polled on top! The source code for Anthony's program is given below.

Congratulations to Anthony, and congratulations to all of the entrants and forum members who responded so enthusiastically to the challenge!

---Tom Nally, Editor


The source code for Anthony's draw.bas is give below. Note that the length of some of the code lines may extend beyond the right edge of your screen, depending on your monitor settings. If copying the code from this page is too difficult, you may find a copy of draw.bas in the zip archive that accompanies this newsletter, nl30.zip. ---Ed.


'Lb Contest entry, for Qbasic draw commands
'as issued by Alyce Watson
'Written by Anthony Webb
'All commands appear to work in QB4.5
nomainwin
dim cmdlist$(20)
gosub [fillarray]
WindowWidth=800
WindowHeight=600
UpperLeftX=10
UpperLeftY=10
menu #main,"File","New File",[newfile],|,"Open File",[openfile],|,"Save File",[savefile],"Save File As",[savefileas],|,"Exit",[quit]
menu #main,"Edit"
menu #main,"Demo","Show Demo",[showdemo]
menu #main,"Image","Save Image",[saveimage]
graphicbox #main.draw,0,0,600,400
graphicbox #main.colors,605,150,185,215
statictext #main.tex3,"Color List",670,133,100,15
statictext #main.tex1,"Command  Discription",605,10,157,15
listbox #main.cmds,cmdlist$(),[showcommand],605,25,57,100
statictext #main.cmddisc," ",665,25,125,100
statictext #main.tex2,"Commands",5,405,100,20
statictext #main.tex4,"X=";lpx,200,405,40,20
statictext #main.tex5,"Y=";lpy,260,405,40,20
statictext #main.tex6,"#Cmds=";ncmds,320,405,80,20
texteditor #main.cmdtb,5,425,785,120
button #main.cmdex,"Draw",[cmdlineexecute],ul,655,375,80,20
open "Draw" for window_nf as #main
#main "trapclose [quit]"
#main.tex1 "!font arial 8 bold underscore"
#main.tex2 "!font arial 10 bold"
#main.cmds "selectindex 1"
#main.tex3 "!Font arial 10 bold"
#main.tex4 "!Font arial 10 bold"
#main.tex5 "!Font arial 10 bold"
#main.tex6 "!Font arial 10 bold"
#main.cmdtb "!font arial 12"
gosub [showcolors]
rflag=1
gosub [showdemo]
goto [showcommand]
wait

[quit]
#main.cmdtb "!contents? cmdline$";   'get the information from the text editor
if cmdline$<>"" then
 confirm "File not Saved, DO you wish to save it?"; answer$
 if answer$="yes" then
  rt=1
  gosub [savefile]
 end if
end if
close #main
end

'--------------------------------fillarray--------------------------------------
'Used to fill the array for command informtion. This is a help feature only and
'has nothing to do with the actual operation of the function
'-------------------------------------------------------------------------------
[fillarray]
cmdlist$(1)="Un"
cmdlist$(2)="Dn"
cmdlist$(3)="Ln"
cmdlist$(4)="Rn"
cmdlist$(5)="En"
cmdlist$(6)="Fn"
cmdlist$(7)="Gn"
cmdlist$(8)="Hn"
cmdlist$(9)="Mx,y"
cmdlist$(10)="B"
cmdlist$(11)="N"
cmdlist$(12)="An"
cmdlist$(13)="TAn"
cmdlist$(14)="Cn"
cmdlist$(15)="Sn"
cmdlist$(16)="Pp,b"
return

'--------------------------------showcommand------------------------------------
'used in conjunction with fillarray, displays information about the command
'selected from the commands list
'-------------------------------------------------------------------------------
[showcommand]
#main.cmds "selection? selected$"
select case selected$
 case "Un"
  #main.cmddisc "Move forward from the current position by n pixels"
 case "Dn"
  #main.cmddisc "Move opposite from the forward position by n pixels"
 case "Ln"
  #main.cmddisc "Move left from the current position by n pixels"
 case "Rn"
  #main.cmddisc "Move right from the current position by n pixels"
 case "En"
  #main.cmddisc "Move diagonally up and right from the current position by n pixels"
 case "Fn"
  #main.cmddisc "Move diagonally down and right from the current position by n pixels"
 case "Gn"
  #main.cmddisc "Move diagonally down and left from the current position by n pixels"
 case "Hn"
  #main.cmddisc "Move diagonally up and left from the current position by n pixels"
 case "Mx,y"
  #main.cmddisc "Move to coordinate x,y. If x is preceded by a + or -, the movement is relative to the last point referenced (LPR)"
 case "B"
  #main.cmddisc "A prefix command. Next movement command moves but doesn't plot"
 case "N"
  #main.cmddisc "A prefix command. Next movement command moves, but returns immediately to previous point"
 case "An"
  #main.cmddisc "Set angle. n may be 0, for 0 degrees; 1, for 90 degrees; 2, for 180 degrees; or 3, for 270 degrees. Rotated figures are rescaled to adjust to the CGA's 4/3 aspect ratio"
 case "TAn"
  #main.cmddisc "Turn angle. n may range from -360 degrees to +360 degrees. Positive values cause counterclockwise rotation; negative values cause clockwise rotation"
 case "Cn"
  #main.cmddisc "Set color to n. The default color for medium-resolution is 3; high-resolution default color is 1. See PALETTE for a list of legal colors"
 case "Sn"
  #main.cmddisc "Set scale factor. n may range from 1 to 255. The scaling factor used is n/4. The default for n is 4"
 case "Pp,b"
  #main.cmddisc "Fill figure color to paint, stopping at areas of color boundary. See PALETTE for a list of legal colors"
end select
wait

'----------------------------cmdlineexecute-------------------------------------
'used to get the information from the texteditor and then call the draw function
'-------------------------------------------------------------------------------
[cmdlineexecute]
#main.draw "cls;north;place 0 0"     'reset the drawing point on the screen to the upper left hand corner
lpx=0                                'reset the last point referenced x
lpy=0                                'reset the last point referenced y
#main.cmdtb "!contents? cmdline$";   'get the information from the text editor
if len(cmdline$)>0 then              'check to make sure that there is information
 a=draw(cmdline$)                    'call the draw function
else
 notice "No commands"                'if no information, then let the user know
end if
wait                                 'drawing complete



'------------------------------showcolors---------------------------------------
'Show a list of colors on the screen to help the user
'-------------------------------------------------------------------------------
[showcolors]
#main.colors "down;place 25 10;color black;backcolor black;boxfilled 75 30"
#main.colors "place 25 35;color black;backcolor darkblue;boxfilled 75 55"
#main.colors "place 25 60;color black;backcolor darkgreen;boxfilled 75 80"
#main.colors "place 25 85;color black;backcolor darkcyan;boxfilled 75 105"
#main.colors "place 25 110;color black;backcolor darkred;boxfilled 75 130"
#main.colors "place 25 135;color black;backcolor darkpink;boxfilled 75 155"
#main.colors "place 25 160;color black;backcolor brown;boxfilled 75 180"
#main.colors "place 25 185;color black;backcolor lightgray;boxfilled 75 205"
#main.colors "place 125 10;color black;backcolor darkgray;boxfilled 175 30"
#main.colors "place 125 35;color black;backcolor blue;boxfilled 175 55"
#main.colors "place 125 60;color black;backcolor green;boxfilled 175 80"
#main.colors "place 125 85;color black;backcolor cyan;boxfilled 175 105"
#main.colors "place 125 110;color black;backcolor red;boxfilled 175 130"
#main.colors "place 125 135;color black;backcolor pink;boxfilled 175 155"
#main.colors "place 125 160;color black;backcolor yellow;boxfilled 175 180"
#main.colors "place 125 185;color black;backcolor white;boxfilled 175 205"
#main.colors "place 5 25;|0="
#main.colors "place 5 50;|1="
#main.colors "place 5 75;|2="
#main.colors "place 5 100;|3="
#main.colors "place 5 125;|4="
#main.colors "place 5 150;|5="
#main.colors "place 5 175;|6="
#main.colors "place 5 200;|7="
#main.colors "place 105 25;|8="
#main.colors "place 105 50;|9="
#main.colors "place 97 75;|10="
#main.colors "place 97 100;|11="
#main.colors "place 97 125;|12="
#main.colors "place 97 150;|13="
#main.colors "place 97 175;|14="
#main.colors "place 97 200;|15="
#main.colors "flush"
return

'-------------------------------------------------------------------------------
'this is the main function for this program. First lets name and explain the
'variables used.
'scale - Used to scale the drawings. based on QB's scale command where n/4 equals
'        the scale. The default is for scale is 4, meaning 4/4=1 or actual size.
'lptr  - This marks the current position within the draw$ that the parser is
'        looking at.
'c$    - the actual string that is passed to the draw command, that contains the
'        the sequence of commands.
'goback - a flag that tells the interpeter to move back to the original starting
'        point. Used with the n prefix command.
'noplot - a flag used to tell the interpeter not to draw. Used with the b prefix
'        command.
'rpos  - Flag used to tell the interpeter if the current move command is to be
'        relative to the last refernced point or is an absolute screen position.
'ncmd  - counter used to count the total number of commands.
'paint - temp variable used to hold the fill color in the paint command
'boundary - temp variable used to hold the bourder color for the paint command
'type  - used to tell the "ExtFloodFill" dll command what type of fill to use.

function draw(c$)
scale=1                            'set the scale to 1
lpx=0                              'set the last point referenced x to upper left corner
lpy=0                              'set the last point referenced y to upper left corner
#main.draw "posxy lpx lpy;down"    'place the point and set the pen down.
clen=len(c$)                       'get the length of the command string
lptr=1                             'set the pointer at 1 to start parsing the command string
while lptr<=clen                   'start the loop, check for the end of the loop.
 #main.tex4 "X=";lpx               'update the last referenced point x label
 #main.tex5 "Y=";lpy               'upadte the last referenced point y label
 #main.tex6 "#cmds=";ncmd          'upadte the number of commands label
 a$=mid$(c$,lptr,1)                'get a character from the command string
 select case a$                    'start the case statement
  case ";"                         'if the letter is a semicolon ignore it
   'ignore this command
   lptr=lptr+1                     'advance the command pointer
  case "S","s"                     'this is the scale command
   lptr=lptr+1                     'advance the command pointer
   gosub [getvalue]                'get the value for the scale command
   if v<1 or v>255 then            'check that the value is a good value
    notice "Error in scale command, ignoring"
   else
    scale=v/4                      'set the scale
   end if
   ncmd=ncmd+1                     'add one to the # of commands variable
  case "N","n"                     'This is the move but return prefix
   goback=1                        'set the goback flag
   lptr=lptr+1                     'advance the command pointer
   ncmd=ncmd+1                     'add one to the # of commands varianle
  case "B","b"                     'this is the move but dont plot prefix
   noplot=1                        'set the noplot flag
   #main.draw "up"                 'raise the pen
   lptr=lptr+1                     'advance the pointer
   ncmd=ncmd+1                     'add one to the # of commands variable
  case "M","m"                     'this is the move command
   lptr=lptr+1                     'advance the command pointer
   if mid$(c$,lptr,1)="+" then     'if next command is a plus
    rpos=1                         'set the relative position flag
    lptr=lptr+1                    'advance the command pointer
   end if
   if mid$(c$,lptr,1)="-" then     'if the next command is a minus
    rpos=2                         'set the relative position flag
    lptr=lptr+1                    'advance the command pointer
   end if
   gosub [getvalue]                'get the x value of the move command
   nx=v                            'nx holds the temp x value
   lptr=lptr+1                     'advance the command pointer
   gosub [getvalue]                'get the y value of the move command
   ny=v                            'ny holds the temp y value
   if rpos=0 then                                  'if rpos=0 then the move is absolute
    #main.draw "line ";lpx;" ";lpy;" ";nx;" ";ny   'draw a line from the last positon to the next position
    lpx=nx                                         'set the last references point x
    lpy=ny                                         'set the last referenced point y
     #main.draw "place ";lpx;" ";lpy               'place the last referenced point for the screen
    ncmd=ncmd+1                                    'advance the # of commands variable
   else                                                     'go to this section if relative position flag is set
    if rpos=1 then                                          'if relative position is one then add the relative position
     #main.draw "line ";lpx;" ";lpy;" ";lpx+nx;" ";lpy+ny    'draw a line from the last position to the next position
     lpx=lpx+nx                                              'set the last referenced position x
     lpy=lpy+ny                                              'set the last referenced position y
     #main.draw "place ";lpx;" ";lpy                         'place the last referenced position point for the screen
     rpos=0                                                  'reset the relative position flag
     ncmd=ncmd+1                                             'add one to the # of commands variable
    else                                                     'if the relative position flag is greater than one then subtract the values
     #main.draw "line ";lpx;" ";lpy;" ";lpx-nx;" ";lpy-ny    'draw a line from the last reference point to the new reference point
     lpx=lpx-nx                                              'set the last referenced point x
     lpy=lpy-ny                                              'set the last referenced point y
     #main.draw "place ";lpx;" ";lpy                         'place the last referenced position point for the screen
     rpos=0                                                  'reset the relative position flag
     ncmd=ncmd+1                                             'add one to the # of commands variable
    end if
   end if
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "U","u"                                               'this is the up command
   lptr=lptr+1                                               'advance the command pointer
   gosub [getvalue]                                          'get the value
   #main.draw "go ";v*scale                                  'advance in the forward position by v(value) times the scale
   if goback=0 then                                          'check to see if the goback flag is set and if it is goback
    #main.draw "posxy lpx lpy;down"                          'get the last referenced point and store it in the variables
   else
    #main.draw "place ";lpx;" ";lpy                          'set the last referenced point
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "D","d"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn 180;go ";v*scale;";turn 180"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "L","l"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn -90;go ";v*scale;";turn 90"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "R","r"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn 90;go ";v*scale;";turn -90"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "C","c"
   lptr=lptr+1
   gosub [getvalue]
   color=v
   gosub [setcolor]
   ncmd=ncmd+1
  case "E","e"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn 45;go ";v*scale;";turn -45"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "F","f"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn 135;go ";v*scale;";turn -135"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "G","g"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn -135;go ";v*scale;";turn 135"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   goback=0
  case "H","h"
   lptr=lptr+1
   gosub [getvalue]
   #main.draw "turn -45;go ";v*scale;";turn 45"
   if goback=0 then
    #main.draw "posxy lpx lpy;down"
   else
    #main.draw "place ";lpx;" ";lpy
   end if
   ncmd=ncmd+1
   goback=0
  case "A","a"
   lptr=lptr+1
   gosub [getvalue]
   select case
    case 0
     #main.draw "north"
    case 1
     #main.draw "north;turn 90"
    case 2
     #main.draw "north;turn 180"
    case 3
     #main.draw "north;turn 270"
    end select
   ncmd=ncmd+1
   goback=0
   case "T","t"
    lptr=lptr+1
    if upper$(mid$(c$,lptr,1))="A" then
     lptr=lptr+1
     gosub [getvalue]
     #main.draw "north;turn ";(v*-1);
    end if
   ncmd=ncmd+1
   goback=0
  case "P","p"
   #main.draw "down"                                         'set the pen down incase the b prefix was used
   lptr=lptr+1
   gosub [getvalue]
   paint=v
   lptr=lptr+1
   gosub [getvalue]
   boundary=v
   hwin=hwnd(#main.draw)
   type=0
   'oldcolor=color
   color=paint
   gosub [setcolor]
   #main.draw "backcolor ";clr$
   color=boundary
   gosub [setcolor]
   CallDll #user32, "GetDC",hwin as long,hDC as long
   calldll #gdi32,"ExtFloodFill" ,_
               hDC as long,_
               lpx as long,_
               lpy as long,_
               crColor as long,_
               type as long,_
               result as long
   color=paint
   gosub [setcolor]
   #main.draw "color ";clr$
   ncmd=ncmd+1
   goback=0
  case else
   lptr=lptr+1
 end select
wend
goto [end]

[setcolor]
 select case color
  case 0
   clr$="black"
   crColor=0
  case 1
   clr$="darkblue"
   crColor=8388608
  case 2
   clr$="darkgreen"
   crColor=32768
  case 3
   clr$="darkcyan"
   crColor=8421376
  case 4
   clr$="darkred"
   crColor=128
  case 5
   clr$="darkpink"
   crColor=8388736
  case 6
   clr$="brown"
   crColor=32896
  case 7
   clr$="lightgray"
   crColor=12632256
  case 8
   clr$="darkgray"
   crColor=8421504
  case 9
   clr$="blue"
   crColor=16711680
  case 10
   clr$="green"
   crColor=65280
  case 11
   clr$="cyan"
   crColor=16776960
  case 12
   clr$="255 0 0"
   crColor=255
  case 13
   clr$="pink"
   crColor=16711935
  case 14
   clr$="yellow"
   crColor=65535
  case 15
   clr$="white"
   crColor=16777215
 end select
 #main.draw "color ";clr$
 return

[getvalue]
 v$=""
 while instr("0123456789-+. ",mid$(c$,lptr,1))>0
  v$=v$+mid$(c$,lptr,1)
  lptr=lptr+1
 wend
 v=val(v$)
 return

[end]
#main.draw "getbmp temp 0 0 600 400"
#main.draw "drawbmp temp 0 0"
#main.draw "flush"
unloadbmp "temp"
end function

[openfile]
filedialog "Open Draw File","*.txt",f$
if f$="" then wait
open f$ for input as #1
#main.cmdtb "!contents #1"
close #1
wait
#main.cmdtb dcmd$
#main.draw "cls;north"
lpx=0
lpy=0
a=draw(dcmd$)
wait


[savefile]
#main.cmdtb "!contents? textstr$"
if f$<>"" then
 open f$ for output as #1
 print #1,textstr$
 close #1
 if rt=1 then return
else
 goto [savefileas]
end if
wait

[savefileas]
filedialog "Save File As","*.txt",f$
if f$="" then wait
open f$ for output as #1
print #1,textstr$
close #1
if rt=1 then return
wait

[newfile]
#main.cmdtb "!cls"
lpx=0
lpy=0
#main.draw "cls"
#main.draw "north"
#main.draw "place 0 0"
wait

[showdemo]
#main.draw "cls;north;place 0 0"
lpx=0
lpy=0
d$="c8;s4;bm300,200;u20;l5;u4;r5;u10;h20;r40;g20;d10;r5;d4;l5;d20;l12;be5;p7,8;c8;bu40;bl12;r5;br3;r5;br3;r5;br3;r5;u1;l6;bl3;l5;bl3;l5;bl3;l5;bd12;br12;d32;br3;bu1;u32;br3;bd1;d31;c14;bu47;bl13;s6;u3;h6;u3;h3;u5;e4;u2;e10;r3;e5;f4;d3;f3;d3;f6;d7;g4;d3;g7;l13;be5;bu5;p14,14;c4;h1;u1;h1;l2;u7;e4;r1;e7;f4;d8;g3;d1;l3;d1;g3;d2;h2;u1;be5;bu2;p12,4"
d$=d$+"c0;bl60;bu15;l10;d65;r40;u10;l30;u55;bg5;p9,0;c0;be5;l10;e10;r10;g10;bu5;p1,0;c0;bd5;e10;d55;g10;e5;bu5;p1,0;c0;bd5;e5;r30;g10;bu5;p1,0;c0;bd5;e10;d10;g10;e5;bu5;p1,0;c0;bd5;bg4;br60"
d$=d$+"c0;u65;r30;f10;d18;g10;f10;d17;g10;l30;be10;u18;r15;f10;d4;g10;l15;bu30;u18;r15;f10;d4;g10;l15;bg5;p14,0"
d$=d$+"c0;bu32;bl4;e10;r30;g10;u1;bh5;p6,0;c0;bu3;br11;f10;g10;bu5;p6,0;c0;bd5;e10;d18;g10;e5;bu5;p6,0;c0;bd5;g15;e10;f10;g10;e5;bu5;p6,0;c0;bd5;be6;d17;g10;e5;bu5;p6,0"
d$=d$+"c0;bl33;bd9;e10;nu12;nr14;c12;d5;c0;p6,0;c0;bu35;ng11;nu11;nr14;bd5;p6,0"
a=draw(d$)
if rflag=1 then
 rflag=0
 return
end if
wait

[saveimage]
filedialog "Save Image as","*.bmp",bf$
if bf$="" then wait
#main.draw "getbmp temp 0 0 600 400"
bmpsave "temp",bf$
unloadbmp "temp"
notice "Image saved as ";bf$
wait